Interpretabilidad con LIME#
Importación de Librerías#
import joblib
import warnings
warnings.filterwarnings("ignore")
import os
import pandas as pd
import numpy as np
import lime
import lime.lime_tabular
from sklearn.model_selection import train_test_split
Carga de modelos#
Procedemos a realizar la carga de modelos con el fin de evaluar su rendimiento en instancias específicas del dataset.
ruta_modelos = os.path.join("..", "app/models.joblib")
final_models = joblib.load(ruta_modelos)
Tratamiento de datos#
ruta_data = os.path.join("..", "data/churn.csv")
df = pd.read_csv(ruta_data)
binarias = [col for col in df.columns if df[col].nunique() == 2]
map_bin = {
'Yes': 1, 'Male': 1,
'No': 0, 'Female': 0,
}
df[binarias] = df[binarias].replace(map_bin)
df[binarias]
#limpieza de NA realizada en análisis exploratorio
df.replace(' ', np.nan, inplace=True)
df = df.dropna()
Separación conjuntos Train - Test#
Se implementa una división del dataset utilizando el parámetro random_state=42 de manera consistente en todos los procesos de segmentación, garantizando así la reproducibilidad exacta de los conjuntos de entrenamiento y prueba.
X = df.drop(["Churn", "customerID"], axis=1)
y = df["Churn"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
LIME#
Para comprender el comportamiento de cada modelo a nivel de predicciones individuales y generar explicaciones interpretables de sus decisiones, se implementó el framework LIME (Local Interpretable Model-agnostic Explanations). Esta técnica de explicación local permite analizar caso por caso cómo las diferentes características influyen en la predicción final para instancias específicas, aproximando localmente el modelo complejo mediante un interpretable que destaca las variables más relevantes en la clasificación de cada cliente particular.
for name, model in final_models.items():
print(f"\n=== {name.upper()} ===")
preprocessing = model.named_steps["preprocessing"]
classifier = model.named_steps["classifier"]
X_train_transformed = preprocessing.transform(X_train)
X_train_transformed = np.array(X_train_transformed, dtype=float)
X_test_transformed = preprocessing.transform(X_test)
X_test_transformed = np.array(X_test_transformed, dtype=float)
explainer = lime.lime_tabular.LimeTabularExplainer(
training_data=X_train_transformed,
feature_names=preprocessing.get_feature_names_out(),
class_names=["No Churn", "Churn"],
mode="classification"
)
i = 30
data_row = X_test_transformed[i]
pred_fn = lambda x: classifier.predict_proba(x)
exp = explainer.explain_instance(
data_row=data_row,
predict_fn=pred_fn,
num_features=10
)
pred = model.predict(X_test.iloc[[i]])[0]
proba = model.predict_proba(X_test.iloc[[i]])[0][1]
print(f"Predicción: {pred} | Probabilidad de Churn: {proba:.4f} | Real: {y_test.iloc[i]}")
exp.show_in_notebook(show_table=True)
=== RANDOM FOREST ===
Predicción: 0 | Probabilidad de Churn: 0.4685 | Real: 0
=== XGBOOST ===
Predicción: 0 | Probabilidad de Churn: 0.2980 | Real: 0
=== CATBOOST ===
Predicción: 1 | Probabilidad de Churn: 0.5143 | Real: 0
=== LIGHTGBM ===
Predicción: 0 | Probabilidad de Churn: 0.2500 | Real: 0
Contexto General
Predicción: lo que el modelo predijo para el cliente (0 = No Churn, 1 = Churn)
Probabilidad: nivel de confianza del modelo en que el cliente hará churn
Real: valor verdadero de la etiqueta en los datos de prueba, para este caso seleccionado la etiqueta es 0 (No Churn)
Random Forest
Predijo No Churn con probabilidad de churn del 46.85%
Factores que empujaban hacia “No Churn”:
cat_Contract_One year= 1.00 (contrato de un año)num_TotalCharges= 4563.00 (altos cargos totales)num_tenure= 47.00 (alta antigüedad)cat_PaymentMethod_Electronic check= 0.00 (no paga con cheque electrónico)
Variables que empujaban hacia “Churn”:
cat_InternetService_Fiber optic= 1.00 (servicio de fibra)num_MonthlyCharges= 95.20 (cargos mensuales altos)
Random Forest mostró la mayor incertidumbre entre todos los modelos con predicción correcta (46.85% de probabilidad de churn), pero finalmente se inclinó por No Churn debido al peso de los factores protectores
XGBoost
Predijo No Churn con probabilidad de churn del 29.8%
Factores que empujaban hacia “No Churn”:
cat_Contract_One year= 1.00 (contrato de un año)num_tenure= 47.00 (alta antigüedad)num_TotalCharges= 4563.00 (altos cargos totales)
Variables que empujaban hacia “Churn”:
cat_InternetService_Fiber optic= 1.00 (servicio de fibra)num_MonthlyCharges= 95.20 (cargos mensuales altos)
A pesar de tener factores de riesgo (fibra y precio alto), los contratos largos y alta antigüedad prevalecieron para una predicción correcta de No Churn
CatBoost
Predijo Churn con probabilidad de churn del 51.43% - PREDICCIÓN INCORRECTA
Factores que empujaban hacia “No Churn”:
cat_Contract_One year= 1.00 (contrato de un año)num_TotalCharges= 4563.00 (altos cargos totales)num_SeniorCitizen= 0.00 (no es adulto mayor)
Variables que empujaban hacia “Churn”:
cat_InternetService_Fiber optic= 1.00 (servicio de fibra)num_MonthlyCharges= 95.20 (cargos mensuales altos)cat_StreamingMovies_Yes= 1.00 (tiene streaming de películas)
CatBoost fue demasiado sensible a los factores de riesgo (fibra + precio alto) ignorando las señales protectoras fuertes (contrato anual + alta antigüedad), resultando en una falsa alarma
LightGBM
Predijo No Churn con probabilidad de churn del 25.0%
Factores que empujaban hacia “No Churn”:
cat_Contract_One year= 1.00 (contrato de un año)cat_PaymentMethod_Electronic check= 0.00 (no paga con cheque electrónico)num_TotalCharges= 4563.00 (altos cargos totales)
Variables que empujaban hacia “Churn”:
cat_InternetService_Fiber optic= 1.00 (servicio de fibra)cat_StreamingMovies_Yes= 1.00 (tiene streaming de películas)num_MonthlyCharges= 95.20 (cargos mensuales altos)
El modelo mostró un balance similar entre factores protectores y de riesgo, pero con menor probabilidad de churn que XGBoost
Resumen de la Evaluación Comparativa de Modelos#
Modelo |
Predicción |
Prob. Churn |
Real |
¿Correcto? |
Principales variables influyentes |
|---|---|---|---|---|---|
Random Forest |
No Churn |
0.46 |
0 |
Sí |
Contract_Two year=0 (+), InternetService_Fiber optic=1 (+), Contract_One year=1 (–) |
XGBoost |
No Churn |
0.29 |
0 |
Sí |
Contract_Two year=0 (+), MonthlyCharges (+), InternetService_Fiber optic=1 (+) |
Catboost |
Churn |
0.51 |
0 |
No |
Contract_Two year=0 (+), InternetService_Fiber optic=1 (+), Contract_One year=1 (–) |
LightGBM |
No Churn |
0.25 |
0 |
Sí |
Contract_Two year=0 (+), InternetService_Fiber optic=1 (+), Contract_One year=1 (–) |
Nota:
(+) = empuja hacia Churn.
(–) = empuja hacia No Churn.